/******************************************************************************
 * ----------------------------------------------------------------------------
 * ACA ASSIGNMENT 3: Simulation of MOESI cache coherence
 * Name: Shriram Gurumoorthy
 * GTID: 902535405
 * Email: shriram.g@gatech.edu
 *
 * This file has the main function and functions used to simulate
 * the MOESI cache coherence
 * ----------------------------------------------------------------------------
 * ***************************************************************************/

#include "moesi.h"

/******************************************************************************
 * function name: main
 * arguements: command line arguements
 * return: int - 0 indicates normal
 *             - 1 indicates error
 * uses: Entry point of the program
******************************************************************************/
int main(int argc, char **argv)
{ int i=0;
  int pending = TRUE;
  FILE *proc[NUM_CORE];
  time = 0;
  int cacheFlag = 0;
  int change = 0;

  printf("\n-------------------------------------------------------------\n");
  printf("Use the following format:\n"
  "  For Direct mapped cache   -->  ./moesiSim dm\n"
  "  For Set Assosiative cache -->  ./moesiSim sa\n");
  printf("-------------------------------------------------------------\n");
 
  if(argc < 2)
    error("Arguement missing. use the same format as shown in the example");
  
  if((strcmp(argv[1],"dm") == 0) || (strcmp(argv[1],"DM") == 0))
    cacheFlag = Edm;
  else if((strcmp(argv[1],"sa") == 0) || (strcmp(argv[1],"SA") == 0))
    cacheFlag = Esa;
  else
    error("Invalid cache type. Use either DM or SA");
  
  if ((proc[0] = fopen("traces/p0.tr", "r")) == NULL) 
    error("trace for processor 0 is missing."
	"\nplace the trace p0.tr in traces folder");
  if ((proc[1] = fopen("traces/p1.tr", "r")) == NULL) 
    error("trace for processor 1 is missing."
	"\nplace the trace p1.tr in traces folder");
  if ((proc[2] = fopen("traces/p2.tr", "r")) == NULL) 
    error("trace for processor 2 is missing."
	"\nplace the trace p2.tr in traces folder");
  if ((proc[3] = fopen("traces/p3.tr", "r")) == NULL) 
  	error("trace for processor 3 is missing."
	"\nplace the trace p3.tr in traces folder");

  switch(cacheFlag)
  {
    case Edm:
      DMNumCacheLine = DMSize/DMCacheLineSize;
      DMIndexBits = getBitCount(DMNumCacheLine);
	  DMOffsetBits = getBitCount(DMCacheLineSize);
	  break;
    case Esa:
      SANumCacheLine = SASize/SACacheLineSize/SANumber;
      SAIndexBits = getBitCount(SANumCacheLine);
	  SAOffsetBits = getBitCount(SACacheLineSize);
	  break;
	default:
	  error("Invalid Cache type");
	  break;
  }

  while(pending)
  { time++;
    for(i=0;i<NUM_CORE;i++)
    {
      if(P[i].valid == FALSE)
	  { if(!feof(proc[i]))
	    { P[i].valid = TRUE;
	      fscanf(proc[i],"%d %d %x",&P[i].time,&P[i].mode,&P[i].addr);
		}
		else
		{ change = change | (powerOfTwo(i));
		}
	  }
    }
    /*If all 4 LSB bits of change are set then exit*/
	if(change == 15)
	  pending = FALSE;

	if(busRdFlag == TRUE)
	{
	  switch(cacheFlag)
	  {
	    case Edm:
	      BusRdDMCacheHandler();
		  break;
		case Esa:
		  break;
		default:
		  break;
	  }
	}
	if(busRdXFlag == TRUE)
	{
	  switch(cacheFlag)
	  {
	    case Edm:
	      BusRdXDMCacheHandler();
		  break;
		case Esa:
		  break
		default:
		  break;
	  }
	}
	if(busUpgrFlag == TRUE)
	{
	  switch(cacheFlag)
	  {
	    case Edm:
	      BusUpgrDMCacheHandler();
		  break;
		case Esa:
		  break
		default:
		  break;
	  }
	}

    /* Check if the processor has a read or write */
	if(time == P[0].time)
	{ P[0].valid = FALSE;
	  if(cacheFlag == Edm)
	  { 
	    if(P[0].mode == Eread)
		  ReadDMCacheHandler(0,P[0].addr);
	    if(P[0].mode == Ewrite)
		  WriteDMCacheHandler(0, P[0].addr);
	  }
	}
	if(time == P[1].time)
	{ P[1].valid = FALSE;
	  if(cacheFlag == Edm)
	  {
	    if(P[1].mode == Eread)
		  ReadDMCacheHandler(1,P[1].addr);
	    if(P[1].mode == Ewrite)
		  WriteDMCacheHandler(1, P[1].addr);
	  }
	}
	if(time == P[2].time)
	{ P[2].valid = FALSE;
	  if(cacheFlag == Edm)
	  {
	    if(P[2].mode == Eread)
		  ReadDMCacheHandler(2,P[2].addr);
	    if(P[2].mode == Ewrite)
		  WriteDMCacheHandler(2, P[2].addr);
	  }
	}
	if(time == P[3].time)
	{ P[3].valid = FALSE;
	  if(cacheFlag == Edm)
	  {
	    if(P[3].mode == Eread)
		  ReadDMCacheHandler(3,P[3].addr);
	    if(P[3].mode == Ewrite)
		  WriteDMCacheHandler(3, P[3].addr);
	  }
	}
    /* check if the read or write by the processor has generated a bus request */
  }

  for(i=0;i<NUM_CORE;i++)
    fclose(proc[i]);

  return 0;
}


/******************************************************************************
 * function name: ReadDMCacheHandler
 * arguements: Proc ID and address
 * return: int - none
 * uses: Handles proc reads
******************************************************************************/
void ReadDMCacheHandler(int proc, int addr)
{
  int i = proc;
  int shared = FALSE;
  switch(DMCache[i].state)
  {
    case Einvalid:
	  shared = checkForShare(addr);
	  if(shared == TRUE)
	  {
	    DMCache[i].state = Eexclusive;
		/*send BusRd*/
		busRdFlag = TRUE;
		procOnBus = proc;
		/*get this line into the proc cache*/
	  }
	  else
	  {
	    DMCache[i].state = Eshared;
		/*send BusRd*/
		/*get this line into the proc cache*/
	  }
	break;
	case Emodified:
	  DMCache[i].state = Emodified;
	break;
    case Eexclusive:
	  DMCache[i].state = Eexclusive;
	break;
    case Eshared:
	  DMCache[i].state = Eshared;
	break;
    case Eowned:
	  DMCache[i].state = Eowned;
	break;
    default:
	  error("Invalid state of cache, Restart Simulation");
	break;
  }
}

/******************************************************************************
 * function name: WriteDMCacheHandler
 * arguements: Proc ID and address
 * return: int - none
 * uses: Handles proc writes
******************************************************************************/
void WriteDMCacheHandler(int proc, int addr)
{
  int i = proc;
  switch(DMCache[i].state)
  {
    case Einvalid:
	  DMCache[i].state = Emodified;
	  busRdXFlag = TRUE;
	  procOnBus = proc;
	  /*get this line into the proc cache*/
	break;
	case Emodified:
	  DMCache[i].state = Emodified;
	break;
    case Eexclusive:
	  DMCache[i].state = Emodified;
	break;
    case Eshared:
	  DMCache[i].state = Emodified;
	  busUpgrFlag = TRUE;
	  procOnBus = proc;
	break;
    case Eowned:
	  DMCache[i].state = Emodified;
	  busUpgrFlag = TRUE;
	  procOnBus = proc;
	break;
    default:
	  error("Invalid state of cache, Restart Simulation");
	break;
  }
}

/******************************************************************************
 * function name: BusRdDMCacheHandler
 * arguements: none
 * return: int - none
 * uses: Handle the BusRd messages
******************************************************************************/
void BusRdDMCacheHandler()
{ int i = 0;
  for(i=0;i<NUM_CORE;i++)
  { if(i!= procOnBus)
      HandleBusRd(i);
  }
}

/******************************************************************************
 * function name: BusRdXDMCacheHandler
 * arguements: none
 * return: int - none
 * uses: Handle the BusRdX messages
******************************************************************************/
void BusRdXDMCacheHandler()
{ int i = 0;
  for(i=0;i<NUM_CORE;i++)
  { if(i!= procOnBus)
      HandleBusRdX(i);
  }
}

/******************************************************************************
 * function name: BusUpgrDMCacheHandler
 * arguements: none
 * return: int - none
 * uses: Handle the BusUpgrade messages
******************************************************************************/
void BusUpgrDMCacheHandler()
{ int i = 0;
  for(i=0;i<NUM_CORE;i++)
  { if(i!= procOnBus)
      HandleBusUpgr(i);
  }
}

/******************************************************************************
 * function name: HandleBusRd
 * arguements: proc Id
 * return: int - none
 * uses: handles the Bus Rd messages per processor
******************************************************************************/
void HandleBusRd(int proc)
{
  int i = proc;
  switch(DMCache[i].state)
  {
    case Einvalid:
	  DMCache[i].state = Einvalid;
	break;
    case Emodified:
	  DMCache[i].state = Eowned;
	  /* Flush star */
	break;
    case Eexclusive:
	  DMCache[i].state = Eshared;
	  /* Flush star */
	break;
    case Eshared:
	  DMCache[i].state = Eshared;
	break;
    case Eowned:
	  DMCache[i].state = Eowned;
	  /* Flush star */
	break;
    default:
	  error("Invalid state of cache, Restart Simulation");
	break;
  }
}

/******************************************************************************
 * function name: HandleBusRdX
 * arguements: proc Id
 * return: int - none
 * uses: handles the BusRdX messages per processor
******************************************************************************/
void HandleBusRdX(int proc)
{
  int i = proc;
  switch(DMCache[i].state)
  {
    case Einvalid:
	  DMCache[i].state = Einvalid;
	break;
    case Emodified:
	  DMCache[i].state = Einvalid;
	  /* Flush */
	break;
    case Eexclusive:
	  DMCache[i].state = Einvalid;
	break;
    case Eshared:
	  DMCache[i].state = Einvalid;
	break;
    case Eowned:
	  DMCache[i].state = Einvalid;
	  /* Flush */
	break;
    default:
	  error("Invalid state of cache, Restart Simulation");
	break;
  }
}

/******************************************************************************
 * function name: HandleBusUpgr
 * arguements: proc Id
 * return: int - none
 * uses: handles the Bus Upgr messages per processor
******************************************************************************/
void HandleBusUpgr(int proc)
{
  int i = proc;
  switch(DMCache[i].state)
  {
    case Eshared:
	  DMCache[i].state = Einvalid;
	break;
    case Eowned:
	  DMCache[i].state = Einvalid;
	break;
	case Emodified:
	  DMCache[i].state = Emodified;
	break;
	case Einvalid:
	  DMCache[i].state = Einvalid;
	break;
	case Eexclusive:
	  DMCache[i].state = Eexclusive;
	break;
    default:
	  error("Invalid state of cache, Restart Simulation");
	break;
  }
}

